package Cinema.DataLayer;

import java.io.IOException;
import java.util.ArrayList;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import Utils.XmlParseHelper;
import Cinema.Client;
import Cinema.User;
import Cinema.CinemaEntities.BigSite;
import Cinema.CinemaEntities.Movie;
import Cinema.CinemaEntities.Order;
import Cinema.CinemaEntities.Show;
import Cinema.CinemaEntities.Site;
import Cinema.CinemaEntities.SmallSite;
import Cinema.CinemaEntities.Theater;
import Exceptions.DataAccessException;
import Exceptions.WrongXMLElement;
import Exceptions.XMLParsingException;


/**
 * Represents a method for backing up data by XML.
 * @author Fuchtzeinsoft Team
 *
 */
public class XmlBackup implements IDataBackup {
	
	private String path;
	
	public XmlBackup(String dbpath) {
		path = dbpath;
	}


	//--------------------------  PUBLIC STATIC HELP FUNCTIONS -------------------------------------

	/**
	 * creates new document with element returned from getXml of IXmlParsable argument
	 * @param parsable - object which getXml method will be called
	 * @return new created document
	 */
	public static Document createDocFromXmlParsableObject(IXmlParsable parsable){
		Document document = XmlParseHelper.createNewDocument();
		Element element = parsable.getXml(document);
		document.appendChild(element);
		return document;
	}

	//--------------------------  HELP FUNCTIONS FOR DEBUGGING -------------------------------------
	
	/**
	 * makes backup to specific path 
	 * @param dataPack - data do save
	 * @param path - specific test path
	 */
	public void doBackapWithGivenPath(DataPackage dataPack, String path){
		
		//setting new path
		String tmp = this.path;
		this.path = path;
		
		//saving data
		saveMovies(dataPack.movies);
		saveSites(dataPack.sites);
		saveUsers(dataPack.users);
		
		//restoring original path
		this.path = tmp;
	}
	
	
	//--------------------------   IDataBackup implementation methods ---------------------------
	@Override
	public void saveMovies(ArrayList<Movie> movies) {
		Document document = XmlParseHelper.createNewDocument();
		Element movieListElement = document.createElement(XmlDefines.MOVIE_LIST);
		document.appendChild(movieListElement);
		for (Movie movie : movies) {
			movieListElement.appendChild(movie.getXml(document));
		}
		XmlParseHelper.saveXmlFile(path+"\\"+MOVIES_XML_FILE_NAME, document);
	}

	@Override
	public void saveSites(ArrayList<Site> sites) {

		Document document = XmlParseHelper.createNewDocument();
		Element siteListElement = document.createElement(XmlDefines.SITE_LIST);
		document.appendChild(siteListElement);
		for (Site site : sites) {
			siteListElement.appendChild(site.getXml(document));
		}
		XmlParseHelper.saveXmlFile(path+"\\"+SITES_XML_FILE_NAME, document);
	}

	@Override
	public void saveUsers(ArrayList<User> users) {
		Document document = XmlParseHelper.createNewDocument();
		Element userListElement = document.createElement(XmlDefines.CLIENT_LIST);
		document.appendChild(userListElement);
		for (User user : users) {
			userListElement.appendChild(user.getXml(document));
		}
		XmlParseHelper.saveXmlFile(path+"\\"+USERS_XML__FILE_NAME, document);
	}

	@Override
	public DataPackage getCinemaData(boolean isRestoring) throws DataAccessException{

		DataPackage resultPackage = new DataPackage();

		if(isRestoring){
			//restoring from backup - parsing three different DB files
			Document documentSites = XmlParseHelper.getDocumentFromFile(path+"\\" + SITES_XML_FILE_NAME);
			resultPackage.sites = loadSites(isRestoring,documentSites.getDocumentElement());
			Document documentMovies = XmlParseHelper.getDocumentFromFile(path+"\\"+MOVIES_XML_FILE_NAME);
			resultPackage.movies = loadMovies(isRestoring,documentMovies.getDocumentElement());
			Document documentUsers = XmlParseHelper.getDocumentFromFile(path+"\\"+USERS_XML__FILE_NAME);
			resultPackage.users = loadUsers(documentUsers.getDocumentElement());
		}
		else{
			//init DB from initial file
			validateInitXmlByChema();
			Document document = XmlParseHelper.getDocumentFromFile(INIT_XML_PATH);

			resultPackage.sites = loadSites(isRestoring, document.getDocumentElement());
			resultPackage.movies = loadMovies(isRestoring, document.getDocumentElement());
			resultPackage.users = new ArrayList<User>();//no users
		}

		//create appropriate DB connections
		connectShowsAndMovies(resultPackage);
		connectShowsAndOrders(resultPackage);

		return resultPackage;
	}


	//--------------------------   help functions for  getCinemaData ---------------------------

	/**
	 * checks if init file is coorect accordinc to xml schema
	 * @throws XMLParsingException - if the file is not correct
	 */
	private void validateInitXmlByChema() throws XMLParsingException{
		try {
		      // define the type of schema - we use W3C:
		      String schemaLang = "http://www.w3.org/2001/XMLSchema";

		      // get validation driver:
		      SchemaFactory factory = SchemaFactory.newInstance(schemaLang);

		      // create schema by reading it from an XSD file:
		      Schema schema = factory.newSchema(new StreamSource(INIT_SCHEMA_PATH));
		      Validator validator = schema.newValidator();

		      // at last perform validation:
		      validator.validate(new StreamSource(INIT_XML_PATH));

		    }catch (SAXException ex) {
		      throw new XMLParsingException();
		    }
			catch (IOException e) {
				//we should not get here
				e.printStackTrace();
			}
	}

	
	/**
	 * parses main xml elemnet to sites
	 * @param mainElement - main xml element
	 * @return ArrayList of parsed sites
	 * @throws WrongXMLElement
	 */
	private ArrayList<Site> loadSites(boolean isRestoring, Element mainElement) throws WrongXMLElement{

		//create new site list
		ArrayList<Site> sites = new ArrayList<Site>();

		//adding sites with cinemas from xml to site list
		ArrayList<Element> siteElements = XmlParseHelper.getChildrenByName(mainElement, XmlDefines.SITE);
		for (Element element : siteElements) {
			sites.add(new BigSite(isRestoring, element));
		}

		//getting element whith holds all the orphaned theaters
		//Element hallElement = XmlBackup.getFirstChildByName(mainElement, StaticDataForXML.SITE_OF_ORPHANED_THEATRES_XML_ELEMNT_NAME);

		//create and fill a list of orphaned theaters - only in init mode
		if (!isRestoring){
			ArrayList<Theater> orphanedTheaters = new ArrayList<Theater>();
			ArrayList<Element> orphanedTheatresElements = XmlParseHelper.getChildrenByName(mainElement, XmlDefines.THEATER);
			for (Element element : orphanedTheatresElements) {
				orphanedTheaters.add(new Theater(isRestoring,element));
			}

			//building enclosing and loving father-site for each orphaned theater and adding the sites to site list
			for (Theater theater : orphanedTheaters) {
				sites.add(new SmallSite(theater));
			}
		}

		return sites;
	}

	/**
	 * parses main xml element to users
	 * @param mainElement - element to parse
	 * @return - ArrayList<User>
	 * @throws WrongXMLElement
	 */
	private ArrayList<User> loadUsers(Element mainElement) throws WrongXMLElement{
		ArrayList<User> resultList = new  ArrayList<User>();
		ArrayList<Element> userElements = XmlParseHelper.getChildrenByName(mainElement, XmlDefines.CLIENT);
		for (Element element : userElements) {
			resultList.add(new Client(element));
		}
		return resultList;
	}

	/**
	 * parses main xml element to movies
	 * @param element - element to parse
	 * @return ArrayList<Movie>
	 * @throws WrongXMLElement
	 */
	private ArrayList<Movie> loadMovies(boolean isRestoring, Element element) throws WrongXMLElement{
		ArrayList<Movie> resultList = new ArrayList<Movie>();

		ArrayList<Element> movieElements = null;
		if (!isRestoring){
			//load from default file
			movieElements =
					XmlParseHelper.convertNodeListToElementArrayList(element.getElementsByTagName(XmlDefines.MOVIE));
		}
		else{
			//load from backup file
			movieElements = XmlParseHelper.getChildrenByName(element, XmlDefines.MOVIE);
		}

		for (Element movieElement : movieElements) {
			Movie movieToAdd = new Movie(movieElement);
			//preventing multiply occurrences of same movie - iterate over parsed elements and search for duplication
			boolean noDuplicationFlag = true;
			for (Movie movie : resultList) {
					if(movie.checkEquals(movieToAdd)){
						noDuplicationFlag = false;
						break;
					}
			}
			//adding if no duplication found - the flag remains be true
			if(noDuplicationFlag){
				resultList.add(movieToAdd);
			}
		}

		return resultList;
	}


	/**
	 * iterates over all movies and shows in the database and creates necessary connections
	 * @param pack - the database
	 */
	private void connectShowsAndMovies(DataPackage pack){
		//iterates over each movie
		for (Movie currentMovie : pack.movies) {
			
			//iterate over each site
			for (Site site : pack.sites) {
				//iterate over each theater in each site
				for (Theater theater : site.getTheaters()) {
					//iterate over each show in each theater in each site
					for (Show currentShow : theater.getShows()) {
						//the core: connecting related shows and movies (same name and genre)
						if(currentMovie.getName().equals(currentShow.getMovieName())
								&&
								currentMovie.getGenre().equals(currentShow.getMovieGenre())){
							currentShow.saveMovie(currentMovie);
						}//end of if
					}//end of for on shows
				}//end of fo on theaters
			}//end of for on sites
		}//end of for on movies
	}

	/**
	 * iterates over all movies and rders in the database and creates necessary connections
	 * @param pack
	 */
	private void connectShowsAndOrders(DataPackage pack){
		//iterate over each client
		for (User user : pack.users) {
			if(!user.getClass().equals(Client.class)){
				continue;
			}
			Client client = (Client) user;
			//iterate over each order
			for (Order order : client.getOrders()) {
				
				//iterate over each site
				for (Site site : pack.sites) {
					//iterate over each theater in each site
					for (Theater theater : site.getTheaters()) {
						//iterate over each show in each theater in each site
						for (Show currentShow : theater.getShows()) {
							//the core: connecting related shows and orders- by id
							if (order.getShowID()==currentShow.getId()){
								order.setShow(currentShow);
							}//end of if
						}//end of for on shows
					}//end of fo on theaters
				}//end of for on sites
				
				
				
			}
		}
	}
	
	//--------------------------   final static data  ----------------------------------------

		private static final String INIT_XML_PATH = ".\\init\\Cinema.xml";
		private static final String INIT_SCHEMA_PATH = ".\\init\\Cinema.xsd";

		private static final String SITES_XML_FILE_NAME = "Sites.xml";
		private static final String MOVIES_XML_FILE_NAME = "Movies.xml";
		private static final String USERS_XML__FILE_NAME = "Users.xml";

}
